﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;

namespace Aim2.Screen {

    class ColorDifference {
        public int R { get; set; }
        public int G { get; set; }
        public int B { get; set; }

        public ColorDifference(int r, int g, int b) {
            R = r;
            G = g;
            B = b;
        }

        public ColorDifference() : this(0, 0, 0) { }

        public ColorDifference(Color a, Color b) : this(a.R - b.R, a.G - b.G, a.B - b.B){
        }
    }

    public class Background : IDisposable {
        List<ILightning> bolts;
        public Tiles Tiles { get; set; }
        public Bars Bars { get; set; }

        private Texture2D gradientTexture;

        float timeBetweenBeats;
        float nextValueTimeBetweenBeats;
        float timeBetweenBeatsHelper;
        float timeElapsed = 0f;

        //const float maximumMovementPerUpdate = 128f;
        
        private GraphicsDevice graphicsDevice;
        private ContentManager contentManager;
        private SpriteBatch spriteBatch;

        Color secondaryColor;
        Color primaryColor;

        Color fromPrimaryColor;
        Color fromSecondaryColor;

        ColorDifference differencePrimary;
        ColorDifference differenceSecondary;

        Color toSecondaryColor;
        public Color ToSecondaryColor { 
            get {
                return toSecondaryColor;
            }
            set {
                fromSecondaryColor = secondaryColor;
                toSecondaryColor = value;
                elapsedToNextSecondaryColor = 0f;
                switchingSecondaryColor = true;
                differenceSecondary = new ColorDifference(toSecondaryColor, fromSecondaryColor);
            }
        }
        public float TimeToNextSecondaryColor { get; set; }
        float elapsedToNextSecondaryColor;
        bool switchingSecondaryColor;

        Color toPrimaryColor;
        public Color ToPrimaryColor {
            get {
                return toPrimaryColor;
            }
            set {
                fromPrimaryColor = primaryColor;
                toPrimaryColor = value;
                elapsedToNextPrimaryColor = 0f;
                switchingPrimaryColor = true;
                differencePrimary = new ColorDifference(toPrimaryColor, fromPrimaryColor);
            }
        }
        public float TimeToNextPrimaryColor { get; set; }
        float elapsedToNextPrimaryColor;
        bool switchingPrimaryColor;

        public float TimeBetweenBeats {
            get {
                return timeBetweenBeats;
            }
            set {
                Bars.Fading = true;
                nextValueTimeBetweenBeats = value;
            }
        }

        public Background(Vector2 position, GraphicsDevice graphicsDevice,
            ContentManager contentManager) {

            if (graphicsDevice == null) {
                throw new ArgumentNullException("graphicsDevice");
            }
            if (contentManager == null) {
                throw new ArgumentNullException("contentManager");
            }

            this.graphicsDevice = graphicsDevice;
            this.contentManager = contentManager;
            bolts = new List<ILightning>();

            primaryColor = new Color(255, 255, 255);
            secondaryColor = Color.Black;
            Tiles = new Tiles();
            Reset(position);
            Bars = new Bars();
            TimeBetweenBeats = 60f / 128f;
            UnFade();
        }

        public void LoadContent() {
            gradientTexture = contentManager.Load<Texture2D>("Textures/gradient");

            Tiles.LoadContent(graphicsDevice, secondaryColor);
            Bars.LoadContent(graphicsDevice);
            new LightningBolt(Vector2.Zero, Vector2.Zero).Load(contentManager);

            spriteBatch = new SpriteBatch(graphicsDevice);
        }

        public void UnloadContent() {
            gradientTexture = null;

            Tiles.UnloadContent();

            if (spriteBatch != null) {
                spriteBatch.Dispose();
                spriteBatch = null;
            }
        }

        private void UnFade() {
            Bars.Fading = false;
            timeBetweenBeats = nextValueTimeBetweenBeats;
            timeBetweenBeatsHelper = (float)(nextValueTimeBetweenBeats * Bars.FactorOfBars) / 2;
            Bars.BuildBarsColors();
        }

        public void Reset(Vector2 position) {
            int viewportWidth = graphicsDevice.Viewport.Width;
            int viewportHeight = graphicsDevice.Viewport.Height;
            
            Tiles.Reset(viewportWidth, viewportHeight, position);
        }

        public static readonly BlendState MaxBlend = new BlendState() {
            AlphaBlendFunction = BlendFunction.Max,
            ColorBlendFunction = BlendFunction.Max,
            AlphaDestinationBlend = Blend.One,
            AlphaSourceBlend = Blend.One,
            ColorDestinationBlend = Blend.One,
            ColorSourceBlend = Blend.One
        };

        public void Draw(Vector2 position) {
            Rectangle backgroundRectangle = new Rectangle(0, 0,
                graphicsDevice.Viewport.Width, graphicsDevice.Viewport.Height);

            spriteBatch.Begin();
            spriteBatch.Draw(Tiles.TileTexture, backgroundRectangle, primaryColor);
            spriteBatch.End();

            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
            spriteBatch.Draw(gradientTexture, backgroundRectangle, null, Color.White, 0.0f,
                Vector2.Zero, SpriteEffects.None, 1.0f);
            spriteBatch.End();

            Tiles.Draw(position, graphicsDevice, spriteBatch, secondaryColor, backgroundRectangle);
            Bars.Draw(spriteBatch, graphicsDevice);

            spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
            foreach (var bolt in bolts)
                bolt.Draw(spriteBatch);
            spriteBatch.End();
        }

        public void Update(float elapsed) {
            Tiles.Update(elapsed);

            UpdateColors(elapsed);

            timeElapsed += elapsed;
            if (timeElapsed >= timeBetweenBeatsHelper) {
                timeElapsed -= timeBetweenBeatsHelper;
                //bolts.Add(new BranchLightning(new Vector2(Game.Random.Next(0, graphicsDevice.Viewport.Width), 0), new Vector2(Game.Random.Next(0, graphicsDevice.Viewport.Width), graphicsDevice.Viewport.Height)));
                //bolts.Add(new LightningBolt(new Vector2(Game.Random.Next(0, graphicsDevice.Viewport.Width), 0), new Vector2(Game.Random.Next(0, graphicsDevice.Viewport.Width), graphicsDevice.Viewport.Height)));
                bolts.Add(new BranchLightning(new Vector2(0, graphicsDevice.Viewport.Height / 2), new Vector2(graphicsDevice.Viewport.Width, graphicsDevice.Viewport.Height / 2)));
            }
            if (Bars.Update(elapsed, timeElapsed, TimeBetweenBeats, timeBetweenBeatsHelper, graphicsDevice.Viewport.Height))
                UnFade();

            foreach (var bolt in bolts)
                bolt.Update();

            bolts = bolts.Where(x => !x.IsComplete).ToList();

        }

        public void InitTransitionTilesSizeX(float t, float v) {
            Tiles.InitTransitionTilesSizeX(t, v);
        }

        public void InitTransitionTilesSizeY(float t, float v) {
            Tiles.InitTransitionTilesSizeY(t, v);
        }

        public void InitTransitionTilesSpeed(float t, float v) {
            Tiles.InitTransitionTilesSpeed(t, v);
        }

        public void InitTransitionBarsSize(float t, float v) {
            Bars.InitTransitionBarsSize(t, v);
        }

        private void UpdateColors(float elapsed) {
            float percentage;
            if (switchingSecondaryColor) {
                if (secondaryColor.PackedValue == toSecondaryColor.PackedValue) {
                    switchingSecondaryColor = false;
                }
                else {
                    elapsedToNextSecondaryColor += elapsed;

                    percentage = elapsedToNextSecondaryColor / TimeToNextSecondaryColor;
                    if (percentage > 1f)
                        percentage = 1f;

                    secondaryColor.R = (byte)(differenceSecondary.R * percentage + fromSecondaryColor.R);
                    secondaryColor.G = (byte)(differenceSecondary.G * percentage + fromSecondaryColor.G);
                    secondaryColor.B = (byte)(differenceSecondary.B * percentage + fromSecondaryColor.B);
                }
                Tiles.TileTexture.SetData<Color>(new Color[] { secondaryColor });
            }
            if (switchingPrimaryColor) {
                if (primaryColor.PackedValue == toPrimaryColor.PackedValue) {
                    switchingPrimaryColor = false;
                }
                else {
                    elapsedToNextPrimaryColor += elapsed;

                    percentage = elapsedToNextPrimaryColor / TimeToNextPrimaryColor;

                    primaryColor.R = (byte)(differencePrimary.R * percentage + fromPrimaryColor.R);
                    primaryColor.G = (byte)(differencePrimary.G * percentage + fromPrimaryColor.G);
                    primaryColor.B = (byte)(differencePrimary.B * percentage + fromPrimaryColor.B);
                }
            }
        }

        ~Background() {
            Dispose(false);
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) {
            if (disposing) {
                lock (this) {
                    Tiles.Dispose();
                    Bars.Dispose();
                    if (spriteBatch != null) {
                        spriteBatch.Dispose();
                        spriteBatch = null;
                    }
                }
            }
        }
    }
}
